package cc.shacocloud.mirage.starter;

import cc.shacocloud.mirage.utils.AppUtil;
import cc.shacocloud.mirage.utils.ClassUtil;
import cc.shacocloud.mirage.utils.charSequence.StrUtil;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;

import java.io.File;
import java.lang.management.ManagementFactory;
import java.time.Duration;
import java.util.Objects;
import java.util.concurrent.Callable;

/**
 * 记录启动时的应用程序信息
 */
@Slf4j
class StartupInfoLogger {
    
    private final Class<?> sourceClass;
    
    StartupInfoLogger(Class<?> sourceClass) {
        this.sourceClass = sourceClass;
    }
    
    /**
     * 开始启动
     */
    void logStarting(@NotNull Logger applicationLog) {
        if (applicationLog.isInfoEnabled()) {
            applicationLog.info(getStartingMessage().toString());
        }
        if (applicationLog.isDebugEnabled()) {
            applicationLog.debug(getRunningMessage().toString());
        }
    }
    
    /**
     * 启动完成
     */
    void logStarted(@NotNull Logger applicationLog, Duration timeTakenToStartup) {
        if (applicationLog.isInfoEnabled()) {
            applicationLog.info(getStartedMessage(timeTakenToStartup).toString());
        }
    }
    
    @NotNull
    private CharSequence getStartingMessage() {
        StringBuilder message = new StringBuilder();
        appendLocalHost(message);
        appendJavaVersion(message);
        message.append(" 启动 ");
        appendApplicationName(message);
        appendVersion(message, this.sourceClass);
        appendPid(message);
        appendContext(message);
        return message;
    }
    
    @NotNull
    private CharSequence getRunningMessage() {
        StringBuilder message = new StringBuilder();
        message.append("Mirage ");
        appendVersion(message, MirageApplication.class);
        return message;
    }
    
    @NotNull
    private CharSequence getStartedMessage(@NotNull Duration timeTakenToStartup) {
        StringBuilder message = new StringBuilder();
        message.append("应用 ");
        appendApplicationName(message);
        message.append(" 启动成功，耗时：");
        message.append(timeTakenToStartup.toMillis() / 1000.0);
        message.append(" 秒");
        try {
            double uptime = ManagementFactory.getRuntimeMXBean().getUptime() / 1000.0;
            message.append(" (JVM 运行 ").append(uptime).append("秒)");
        } catch (Throwable ignore) {
        }
        return message;
    }
    
    private void appendApplicationName(@NotNull StringBuilder message) {
        Class<?> startClass = Objects.isNull(this.sourceClass) ? AppUtil.getStartClass() : this.sourceClass;
        String name = Objects.nonNull(startClass) ? ClassUtil.getShortName(startClass) : "应用";
        message.append(name);
    }
    
    /**
     * 应用版本号
     */
    private void appendVersion(StringBuilder message, Class<?> source) {
        append(message, "v", () -> source.getPackage().getImplementationVersion());
    }
    
    private void appendLocalHost(StringBuilder message) {
        append(message, "在节点 ", AppUtil::getHostName);
    }
    
    private void appendPid(StringBuilder message) {
        append(message, "PID ", AppUtil::getPid);
    }
    
    private void appendContext(StringBuilder message) {
        StringBuilder context = new StringBuilder();
        
        append(context, "当前用户 ", AppUtil::getUserName);
        append(context, "启动目录 ", () -> AppUtil.getStartDir(this.sourceClass));
        
        File source = AppUtil.findSource(this.sourceClass);
        if (Objects.nonNull(source)) {
            append(context, "应用 ", source::getAbsolutePath);
        }
        
        if (context.length() > 0) {
            message.append(" (");
            message.append(context);
            message.append(")");
        }
    }
    
    private void appendJavaVersion(StringBuilder message) {
        append(message, "使用 Jdk ", AppUtil::getJdkVersion);
    }
    
    private void append(StringBuilder message, String prefix, Callable<Object> call) {
        append(message, prefix, call, "");
    }
    
    private void append(StringBuilder message, String prefix, Callable<Object> call, String defaultValue) {
        Object result = callIfPossible(call);
        String value = (result != null) ? result.toString() : null;
        if (StrUtil.isEmpty(value)) {
            value = defaultValue;
        }
        if (StrUtil.isNotEmpty(value)) {
            message.append((message.length() > 0) ? " " : "");
            message.append(prefix);
            message.append(value);
        }
    }
    
    @Nullable
    private Object callIfPossible(Callable<Object> call) {
        try {
            return call.call();
        } catch (Exception ex) {
            return null;
        }
    }
    
}
